package org.apereo.cas.services.web;

import java.util.List;
import java.util.Locale;

import org.apereo.cas.configuration.CasConfigurationProperties;
import org.apereo.cas.services.RegisteredService;
import org.apereo.cas.web.support.WebUtils;
import org.springframework.boot.autoconfigure.template.TemplateLocation;
import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.OrderComparator;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.view.AbstractCachingViewResolver;
import org.springframework.webflow.execution.RequestContext;
import org.springframework.webflow.execution.RequestContextHolder;
import org.thymeleaf.spring5.view.AbstractThymeleafView;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.val;
import lombok.extern.slf4j.Slf4j;

/**
 * {@link ThemeViewResolver} is a theme resolver that checks for a UI view in the specific theme before utilizing the base UI view.
 *
 * @author Daniel Frett
 * @since 5.2.0
 */
@Slf4j
@Setter
@RequiredArgsConstructor
public class ThemeViewResolver extends AbstractCachingViewResolver {

    /**
     * {@link ThemeViewResolverFactory} that will create a {@link ThemeViewResolver} for the specified theme.
     */
    @Getter
    @Setter
    @Slf4j
    @RequiredArgsConstructor
    public static class Factory implements ThemeViewResolverFactory, ApplicationContextAware {

        private final ViewResolver delegate;

        private final ThymeleafProperties thymeleafProperties;

        private final CasConfigurationProperties casProperties;

        private final List<CasThymeleafViewResolverConfigurer> thymeleafViewResolverConfigurers;

        private ApplicationContext applicationContext;

        @Override
        public ThemeViewResolver create(final String theme) {
            LOGGER.trace("Creating theme view resolver based on theme [{}]", theme);
            val resolver = new ThemeViewResolver(delegate, thymeleafProperties, theme, thymeleafViewResolverConfigurers);
            resolver.setApplicationContext(applicationContext);
            resolver.setCache(thymeleafProperties.isCache());
            return resolver;
        }
    }

    private final ViewResolver delegate;

    private final ThymeleafProperties thymeleafProperties;

    private final String theme;

    private final List<CasThymeleafViewResolverConfigurer> thymeleafViewResolverConfigurers;

    private void configureTemplateThemeDefaultLocation(final AbstractThymeleafView thymeleafView) {
        final String baseTemplateName = thymeleafView.getTemplateName();
        // Environment env = this.getApplicationContext().getEnvironment();
        // final String baseTemplateName = env.getProperty("y9.app.y9sso.casLoginView", "casLoginView-luohu");

        final RequestContext requestContext = RequestContextHolder.getRequestContext();
        final RegisteredService registeredService = WebUtils.getRegisteredService(requestContext);
        final String templateName;
        final String defaultThemeName = "theme/" + theme + "/" + baseTemplateName;
        if (registeredService != null && StringUtils.hasText(registeredService.getTheme())) {
            LOGGER.debug("Attempting to locate views for service [{}] with theme [{}]", registeredService.getServiceId(), registeredService.getTheme());
            templateName = "theme/" + registeredService.getTheme() + "/" + baseTemplateName;
        } else {
            templateName = defaultThemeName;
        }

        final String path = thymeleafProperties.getPrefix().concat(templateName).concat(thymeleafProperties.getSuffix());
        LOGGER.trace("Attempting to locate theme location at [{}]", path);
        val location = new TemplateLocation(path);
        val applicationContext = getApplicationContext();
        if (location.exists(applicationContext)) {
            thymeleafView.setTemplateName(templateName);
        } else {
            final TemplateLocation location2 = new TemplateLocation(thymeleafProperties.getPrefix().concat(defaultThemeName).concat(thymeleafProperties.getSuffix()));
            if (location2.exists(applicationContext)) {
                thymeleafView.setTemplateName(defaultThemeName);
            } else {
                if ("error".equals(baseTemplateName)) {
                    thymeleafView.setTemplateName(baseTemplateName);
                } else {
                    thymeleafView.setTemplateName("theme/" + baseTemplateName);
                }
            }
        }
    }

    @Override
    protected Object getCacheKey(final String viewName, final Locale locale) {
        return String.format("%s#%s", theme, super.getCacheKey(viewName, locale));
    }

    @Override
    protected View loadView(final String viewName, final Locale locale) throws Exception {
        LOGGER.trace("Attempting to resolve view [{}] via locale [{}]", viewName, locale);
        val applicationContext = obtainApplicationContext();
        val view = applicationContext.containsBean(viewName) ? applicationContext.getBean(viewName, View.class) : delegate.resolveViewName(viewName, locale);

        if (view instanceof AbstractThymeleafView) {
            val thymeleafView = (AbstractThymeleafView)view;
            configureTemplateThemeDefaultLocation(thymeleafView);

            thymeleafViewResolverConfigurers.stream().sorted(OrderComparator.INSTANCE).forEach(configurer -> configurer.configureThymeleafView(thymeleafView));
        }
        return view;
    }
}
